package com.rengwuxian.coursecoroutines._2_structured_concurrency

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.suspendCoroutine

/**
 * 协程的交互式取消：cancel()
 * Job.cancel()、Job.isActive、delay()、CancellationException
 *
 * 和线程的交互式结束是一样的。
 * val scope = CoroutineScope(EmptyCoroutineContext) // Default
 *   val job = launch(Dispatchers.Default) {
 *   }
 * job.cancel()
 *
 * 1.注意父子协程的关系，父协程会等子协程执行完之后，再结束自己。如果不是自己的子协程，外部的协程可以直接结束。
 *   例子代码中runBlocking的协程，不会等待上面job执行完，会直接执行完后，main()最后一行代码执行完，主线程结束，程序就终止了。
 * 2.Java的规则：当所有的用户线程都结束，程序就退出。守护线程是不管的。
 *   Java里的线程分为用户线程和守护线程，靠Thread中的一个字段 isDaemon来标记的
 *   thread{}.apply { isDaemon = true }
 *   守护线程：守护别的线程的作用，而不具备自身的业务功能。
 *   用户线程：就是正常的线程，具备业务功能的。默认创建用户线程。
 *   Daemon Thread; User Thread
 * 3.协程创建的线程都是守护线程，isDaemon设置为true。程序是不会等待协程的线程的。
 *   main()是本例中唯一的用户线程。
 *
 * 解决方法，恢复协程的父子关系即可。
 *
 * 协程cancel()后，内部调用isActive来判断协程是否出于活跃状态。
 * coroutineContext.job.isActive
 * CoroutineScope本身也有isActive
 * 内部都是一个地方获取的。
 * if (!isActive) {
 *   // clear
 *   // return@launch
 *   throw CancellationException
 * }
 * 协程的结束，应该使用throw CancellationException，协程本身会接住这个异常，并把自己取消。
 * CancellationException是一个正常的异常，只是用来取消协程本身，协程的取消是结构化的，进而会取消所有子协程。
 * 而return，只能结束这个协程代码块的执行。属于不正确使用方式。
 * 等价写法：可以使用ensureActive()
 * 深层原理就是：if (!isActive) throw getCancellationException()
 * 适用直接取消协程，不需要做收尾清理工作。
 *
 * 关键点：检查协程的活跃状态，一旦不活跃了，就结束协程。
 * 结束协程的方式：就是抛异常。
 *
 * delay()本身做了配合取消的工作，一旦不活跃，就抛CancellationException，结束协程。
 * 所以delay()外面不能catch住CancellationException，会吞掉取消。记得重新抛出来。
 *    try {
 *         delay(500)
 *       } catch (e: CancellationException) {
 *         println("Cancelled")
 *         // Clear
 *         throw e
 *       }
 * 如果清理工作一定会做，用finally{}
 *
 * 协程中所有的挂起函数都会抛出CancellationException。
 * 只有一个例外：suspendCoroutine{},没有反应，正常执行。
 *
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext) // Default
  val job = launch(Dispatchers.Default) {
    suspendCoroutine<String> {

    }
    suspendCancellableCoroutine<String> {

    }
    /*var count = 0
    while (true) {
//      ensureActive()
      if (!isActive) {
        // clear
        throw CancellationException()
      }
      count++
      if (count % 100_000_000 == 0) {
        println(count)
      }
      if (count % 1_000_000_000 == 0) {
        break
      }
    }*/
    // InterruptedException
    var count = 0
    while (true) {
      /*if (!isActive) {
        // Clear
        return@launch
      }*/
      println("count: ${count++}")
      try {
        delay(500)
      } /*catch (e: CancellationException) {
        println("Cancelled")
        // Clear
        throw e
      }*/ finally {
        // Clear
      }
    }
  }
  delay(1000)
  job.cancel()
  // Thread.interrupt()
}